自從 2009 年的 Avatar 掀起 3D 電影風潮以來,現在 3D 影像算是挺流行的話題,各家科技廠商相繼推出相關產品,這在今年 Computex 2010 的 3D 主題館可見冰山一角。
3D 視覺是個老話題了,1990年代末期台灣跟日本也流行了好一陣子的 3D 立體書 (stereogram),這裡也就不多做介紹了。這篇要講的主要是一個簡易自製紅藍偏光 3D 照片的方法。
3D 影像
目前主流的 3D 影像,用的都是深度知覺(depth perception)的「雙眼線索」(binocular cues),也就是利用人的雙眼在注視物體時一個微小的角度差,來產生立體感。這類影像被通稱為 stereoscopy,有兩張照片並列的,有疊影的(像是目前主流的 3D 電影),有紅藍色差疊影的,也有混合在雜訊裡隱藏起來的(stereogram)。各種 3D 圖像都有簡易的製作工具,以及相關的原理介紹,請自行參考連結。
Processing
Processing 是一個 Java-based 的 scripting 語言,是個自由軟體,可以到此下載。Processing 常常用來做資料視覺化(Data Visualization)跟互動技術。交大的「互動科技」也有用到這個軟體,所以現在也有中文的教學可以參考。
如果只是要做 3D 圖,倒不必對這個語言有太深的涉獵,只要會安裝並且執行這個軟體即可。
實作程序
這是參考 O'Reilly Answers 上的一篇 DIY 3D p hotography with Processing:
- 對同一個物體連續拍兩張照片,第一張用左眼瞄準,第二張用右眼,基本上兩張的差別只在相機轉一個小小的角度。建議像片的解析度不要用太高,不然 processing 會 out of memory(如果自己會改 Java VM 的記憶體用量的話,則不受此限)。
- 把兩張照片存到電腦裡,因為 processing 認的是絕對路徑,假設兩張照片分別存放在:"/Users/linbay/work/photo_left.jpg" 跟 "/Users/linbay/work/photo_right.jpg"
- 打開 processing,執行以下的 script,記得要把前三行改成相對應的位置(第一行是左眼照片,第二行是右眼照片,第三行是輸出圖檔),程式會自動產生新圖檔,按下 "e" 件則會儲存,按 "esc" 跳出。
PImage left = loadImage("/Users/odewahn/Desktop/lake_l.png"); PImage right = loadImage("/Users/odewahn/Desktop/lake_r.png"); String outfile = "/Users/odewahn/Desktop/out.png"; int rx = 0; // x offest int ry = 0; // y offset int dSize = 10; // # of pixels to shift Intersection overlap; PImage merged; class Intersection { //Defines the overlap reqion int w, h; // width and height of overlap 1 int x, y; // x,y of the top left point of the overlap // Defines the width and height of the 2 reqions. They must be of equal size. int W, H; // Width and height of the 2 regions int rx, ry; // x,y position of region //Defines the mapping of a point p in the overlap into a point in region 1 and region 2 int eqX1, eqY1; int eqX2, eqY2; //Construct a new region Intersection(int _W, int _H) { W = _W; H = _H; } // Set the offset values of region 2 void setOffset (int _rx, int _ry) { rx = _rx; ry = _ry; compute(); } //Computes the intersection between the two image based on the current offset void compute() { if (rx < 0) { if (ry < 0) { // Case A w = W + rx; h = H + ry; x = 0; y = 0; } else { //Case C w = W + rx; h = H - ry; x = 0; y = ry; } } else { if (ry < 0) { //Case B w = W - rx; h = H + ry; x = rx; y = 0; } else { //Case D w = W - rx; h = H - ry; x = rx; y = ry; } } } //Given a point (x,y) in the intersection, this method finds the correspongin point in the 2 regions void findEquivPoint (int x, int y) { if (rx < 0) { if (ry < 0) { // Case A eqX1 = x; eqY1 = y; eqX2 = x - rx; eqY2 = y - ry; } else { //Case C eqX1 = x; eqY1 = y + ry; eqX2 = x - rx; eqY2 = y; } } else { if (ry < 0) { //Case B eqX1 = x + rx; eqY1 = y; eqX2 = x; eqY2 = y - ry; } else { //Case D eqX1 = x + rx; eqY1 = y + ry; eqX2 = x; eqY2 = y; } } } } //Create a merged image PImage merge() { int loc; PImage merged = createImage(overlap.w, overlap.h, RGB); left.loadPixels(); right.loadPixels(); merged.loadPixels(); //now process the left and right pixels one by one and merge them into a new color for (int x = 0; x < overlap.w; x++) { for (int y = 0; y < overlap.h; y++) { overlap.findEquivPoint(x,y); loc = overlap.eqX1 + overlap.eqY1*overlap.W; //Pull out RGB for the left image float r1 = red(left.pixels[loc]); float g1 = green(left.pixels[loc]); float b1 = blue(left.pixels[loc]); // Pull rgb for the right image loc = overlap.eqX2 + overlap.eqY2*overlap.W; float r2 = red(right.pixels[loc]); float g2 = green(right.pixels[loc]); float b2 = blue(right.pixels[loc]); //Compute new pixel color in the merged image float r = 0.299 * r1 + 0.587 * g1 + 0.114 * b1; float g = g2; float b = b2; //Now set new color loc = x + y*merged.width; merged.pixels[loc] = color(r,g,b); } } return merged; } void setup() { size(left.width, left.height); overlap = new Intersection(left.width, left.height); overlap.setOffset(0,0); merged = merge(); } void keyPressed() { switch (key) { case 'q': ry -= dSize; break; case 'a': ry += dSize; break; case 'l': rx += dSize; break; case 'k': rx -= dSize; break; case '1': dSize = 10; break; case '2': dSize = 1; break; case 'e': merged.save(outfile); break; } overlap.setOffset(rx,ry); merged = merge(); } void draw() { background(#ffffff); image(merged,0,0); } |
沒有留言:
張貼留言